package fi.internetix.quercus;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import javax.sql.DataSource;

import com.caucho.util.Log;

/**
 * Simple connection pooler. 
 * 
 * Code is mostly based on hibernate-gae cache region provider implementation: 

 * (http://code.google.com/p/hibernate-gae/source/browse/trunk/hibernate-gae-connection-pool/src/fi/foyt/hibernate/gae/connection/GAEConnectionProvider.java)
 */
public class PooledDataSource implements DataSource {
  
  private static final Logger log = Log.open(PooledDataSource.class);
  private static final String CONNECTION_TEST_SQL = "select 12345 from dual";
  private static final int CONNECTION_TEST_RESULT = 12345;

  
  public PooledDataSource(DataSource originalDataSource) {
    this.originalDataSource = originalDataSource;
  }

  @Override
  public PrintWriter getLogWriter() throws SQLException {
    return originalDataSource.getLogWriter();
  }

  @Override
  public int getLoginTimeout() throws SQLException {
    return originalDataSource.getLoginTimeout();
  }

  @Override
  public void setLogWriter(PrintWriter out) throws SQLException {
    originalDataSource.setLogWriter(out);
  }

  @Override
  public void setLoginTimeout(int seconds) throws SQLException {
    originalDataSource.setLoginTimeout(seconds);
  }

  @Override
  public boolean isWrapperFor(Class<?> iface) throws SQLException {
    return originalDataSource.isWrapperFor(iface);
  }

  @Override
  public <T> T unwrap(Class<T> iface) throws SQLException {
    return originalDataSource.unwrap(iface);
  }

  public Connection getConnection() throws SQLException {
    // essentially, if we have available connections in the pool, use one...
    synchronized (pool) {
      while (!pool.isEmpty()) {
        int last = pool.size() - 1;
        Connection pooled = pool.remove(last);
        if (checkConnectionHealth(pooled)) {
          return pooled;
        } else {
          log.warning("Pooled connection was invalid and thus dropped from pool");
        }

      }
    }

    // otherwise we open a new connection...

    log.fine("Opening new JDBC connection");
    Connection connection = new PooledConnection(this, originalDataSource.getConnection());
    return connection;
  }

  @Override
  public Connection getConnection(String username, String password) throws SQLException {
    return getConnection();
  }

  public void release(PooledConnection connection) throws SQLException {
    // add to the pool if the max size is not yet reached.
    synchronized (pool) {
      int currentSize = pool.size();
      if (currentSize < poolSize) {
        pool.add(connection);
        return;
      }
    }

    log.fine("Closing JDBC connection");
    connection.close();
  }

  public void close() {
    log.info("AppEngine connection provider cleaning up connection pool");

    for (Connection connection : pool) {
      try {
        connection.close();
      } catch (SQLException sqle) {
        log.severe("Unable to closed pooled connection: " + sqle.getMessage());
      }
    }
    pool.clear();

  }
  
  private boolean checkConnectionHealth(Connection connection) {
    try {
      Statement statement = connection.createStatement();
      ResultSet resultSet = statement.executeQuery(CONNECTION_TEST_SQL);
      if (resultSet.next()) {
        return resultSet.getInt(1) == CONNECTION_TEST_RESULT;
      }
    } catch (Exception e) {
      return false;
    }

    return false;
  }

  private int poolSize = 20;
  private DataSource originalDataSource;
  private final List<PooledConnection> pool = new ArrayList<PooledConnection>();
}
